package com.nulldev.util.JVM.reflections.cache;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.nulldev.util.data.Arrays.maps.trie.TrieMap;

final class MethodObject {
	private final Map<String, MethodElement> map = new TrieMap<String, MethodElement>();

	public MethodObject(final Class<?> clazz) {
		Method[] ms = clazz.getDeclaredMethods();
		if (ms != null) {
			int len = ms.length;
			for (int i = 0; i < len; i++) {
				Method m = ms[i];
				if (Modifier.isPublic(m.getModifiers())) {
					String name = m.getName();
					if (map.containsKey(name)) {
						MethodElement f = map.get(name);
						if (f.next == null) {
							f.next = new MethodElement(m);
						} else {
							MethodElement n = f.next;
							f.next = new MethodElement(m);
							f.next.next = n;
						}
					} else {
						map.put(name, new MethodElement(m));
					}
				}
			}
		}
	}

	public boolean isMethod(boolean staticFlg, String name) {
		MethodElement emt = map.get(name);
		while (emt != null) {
			if (emt.isStatic == staticFlg) {
				return true;
			}
			emt = emt.next;
		}
		return false;
	}

	public void getNames(Set<String> out, boolean staticFlg) {
		String name;
		Iterator<String> it = map.keySet().iterator();
		while (it.hasNext()) {
			name = it.next();
			if (map.get(name).isStatic == staticFlg) {
				out.add(name);
			}
		}
	}

	public boolean invoke(Object[] result, Object target, String name, ClassLoader cl, Class<?>[] types, Object[] args) {
		boolean staticFlag = (target == null);
		int argsLen = (args == null) ? 0 : args.length;
		try {
			MethodElement emt = map.get(name);
			if (argsLen == 0) {
				while (emt != null) {
					if (staticFlag == emt.isStatic && argsLen == emt.paramsLength) {
						Object res = emt.method.invoke(target, CachedPrimitive.NO_PARAM);
						if (result != null) {
							result[0] = res;
						}
						return true;
					}
					emt = emt.next;
				}
			} else {
				int pf = 100 * argsLen;
				int score = -1;
				MethodElement targetEmt = null;
				while (emt != null) {
					if (staticFlag == emt.isStatic && argsLen == emt.paramsLength) {
						int sc = Util.parmasScore(emt.primitives, emt.params, types, args, cl);
						if (sc != -1 && score < sc) {
							if (sc == pf) {
								Object res = emt.method.invoke(target, args);
								if (result != null) {
									result[0] = res;
								}
								return true;
							}
							score = sc;
							targetEmt = emt;
						}
					}
					emt = emt.next;
				}
				if (targetEmt != null) {
					args = Util.convertParams(args, targetEmt.params);
					if (args == null) {
						return false;
					}
					Object res = targetEmt.method.invoke(target, args);
					if (result != null) {
						result[0] = res;
					}
					return true;
				}
			}
			return false;
		} catch (InvocationTargetException it) {
			throw new CachedReflectException(it.getCause());
		} catch (Exception e) {
			throw new CachedReflectException(e);
		}
	}
}
